package com.authine.cloudpivot.app.openapi.config;

import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.server.*;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author carter
 * create_date  2020/10/13 14:16
 * description     网关本身异常统一处理
 */
@Configuration
@Slf4j
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ExceptionConfig {

    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public ExceptionConfig(
            ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider<List<ViewResolver>> viewResolversProvider,
            ServerCodecConfigurer serverCodecConfigurer,
            ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.resourceProperties = resourceProperties;
        this.applicationContext = applicationContext;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }


    private static class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

        public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                    ErrorProperties errorProperties, ApplicationContext applicationContext) {
            super(errorAttributes, resourceProperties, errorProperties, applicationContext);
        }

        /**
         * 获取异常属性
         */
        @Override
        protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
            int code = HttpStatus.INTERNAL_SERVER_ERROR.value();
            Throwable error = super.getError(request);
            if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
                code = HttpStatus.NOT_FOUND.value();
            }
            return response(code, this.buildMessage(request, error), error, request);
        }

        /**
         * 指定响应处理方法为JSON处理的方法
         *
         * @param errorAttributes
         */
        @Override
        protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
            return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
        }


        /**
         * 根据code获取对应的HttpStatus
         *
         * @param errorAttributes
         */
        @Override
        protected int getHttpStatus(Map<String, Object> errorAttributes) {
            return HttpStatus.OK.value();
        }

        /**
         * 构建异常信息
         *
         * @param request
         * @param ex
         * @return
         */
        private String buildMessage(ServerRequest request, Throwable ex) {
            StringBuilder message = new StringBuilder("");

            if (ex != null) {
                message.append(": ");
                message.append(ex.getMessage());
            }

            message.append(" 接口:");
            message.append(request.methodName());
            message.append(" ");
            message.append(request.uri());

            return message.toString();
        }

        /**
         * 构建返回的JSON数据格式
         *
         * @param status       状态码
         * @param errorMessage 异常信息
         * @param error
         * @param request
         * @return
         */
        public static Map<String, Object> response(int status, String errorMessage, Throwable error, ServerRequest request) {
            Map<String, Object> map = new HashMap<>();
            map.put("isSuccess", false);

            AtomicReference<String> serviceName = new AtomicReference<>("gateway");
            Optional.of(request.path()).map(item -> item.substring(4))
                    .map(item->item.replaceFirst("/",""))
                    .filter(item->item.contains("/"))
                    .map(item -> {
                        String projectName=item.substring(0, item.indexOf("/"));
                        if ("launcher".equalsIgnoreCase(projectName)){
                            item = item.substring(item.indexOf("/")+1);
                            projectName= "应用app-launcher-"+item.substring(0,item.indexOf("/"));
                        }else{
                            projectName = "平台服务app-"+projectName;
                        }
                       return projectName;
                    })
                    .ifPresent(item -> serviceName.set(item));

            if (errorMessage.contains("SERVICE_UNAVAILABLE")){
                status=503;
                errorMessage= serviceName.get() + "不可用";
            }else if(errorMessage.contains("Connection refused")){
                status=503;
                errorMessage= serviceName.get() + "不可用或者没有启动";
            }
            //兜底处理请求不到的异常
            if (error instanceof NullPointerException){
                status=503;
                errorMessage= serviceName.get() + "不可用";
            }

            map.put("errCode", status);
            map.put("errMessage", errorMessage);
            map.put("timestamp",new Date());


            map.put("correctGuid", "请找负责对应服务的运维或者开发工程师解决问题");
            map.put("data", Throwables.getStackTraceAsString(error));
            log.error("网关返回值:{}", map.toString());
            return map;
        }
    }

}
